package hu.vissy.yacap.display;

import hu.vissy.utils.StringUtils;
import hu.vissy.utils.StringUtils.StringWrapperOptions;
import hu.vissy.yacap.OptionSet;
import hu.vissy.yacap.UnnamedOptionMultiplicity;
import hu.vissy.yacap.display.TextHelpRendererOptions.SortMethod;
import hu.vissy.yacap.option.CommandLineOption;
import hu.vissy.yacap.option.OptionPolicy;
import hu.vissy.yacap.option.ValuePolicy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;

public class TextHelpRenderer implements HelpRenderer<StringBuilder> {

    public static final String INTERNAL_OPTION_SECTION = "-- <internal parameters>";

    public static final String OPTIONS_SIGNATURE_INDICATOR = "[OPTIONS]";

    private TextHelpRendererOptions options;

    /**
     * Constructor.
     *
     * @param options
     *            The renderer options.
     */
    public TextHelpRenderer(TextHelpRendererOptions options) {
        this.options = options;
    }

    public TextHelpRenderer() {
        this(new TextHelpRendererOptions());
    }

    /**
     * @return The renderer options.
     */
    public TextHelpRendererOptions getOptions() {
        return options;
    }


    /*
     * (non-Javadoc)
     * 
     * @see hu.vissy.yacap.display.HelpRenderer#render(hu.vissy.yacap.OptionSet)
     */
    @Override
    public StringBuilder render(OptionSet optionSet) {
        return render(optionSet, new StringBuilder());
    }

    /*
     * (non-Javadoc)
     * 
     * @see hu.vissy.yacap.display.HelpRenderer#render(hu.vissy.yacap.OptionSet,
     * java.lang.StringBuilder)
     */
    @Override
    public StringBuilder render(OptionSet optionSet, StringBuilder sb) {
        renderSignature(optionSet, sb);
        renderDetails(optionSet, sb);
        return sb;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * hu.vissy.yacap.display.HelpRenderer#renderSignature(hu.vissy.yacap.OptionSet
     * , java.lang.StringBuilder)
     */
    @Override
    public void renderSignature(OptionSet optionSet, StringBuilder sb) {
        String cache = "";
        // Adding prefix
        if (options.getSignaturePrefix() != null) {
            cache = options.getSignaturePrefix();
        }

        // Adding indicator if optional options are hidden
        if (!options.isShowOptionalOptionsInSignature()) {
            boolean hasOptional = false;
            for (CommandLineOption<?> o : optionSet.getOptions()) {
                Object op = o.getDefaultOptionPolicy();
                if (op == OptionPolicy.OPTIONAL || op == OptionPolicy.OPTIONAL_AUTO) {
                    hasOptional = true;
                    break;
                }
            }
            if (hasOptional) {
                if (cache.length() != 0) {
                    cache += " ";
                }
                cache += OPTIONS_SIGNATURE_INDICATOR;
            }
        }

        // Sorts options
        List<CommandLineOption<?>> sortedOptions = sortOptionSet(optionSet, options.getSignatureSortMethod());

        // Iterate over options
        for (CommandLineOption<?> o : sortedOptions) {
            Object op = o.getDefaultOptionPolicy();
            // Display only when not hidden and either optional options should be displayed or not optional.
            if (op != OptionPolicy.HIDDEN &&
                    (options.isShowOptionalOptionsInSignature() ||
                    op != OptionPolicy.OPTIONAL && op != OptionPolicy.OPTIONAL_AUTO)) {
                String sig = getOptionSignature(o);
                if (cache.length() != 0 && (options.isSignatureSplit() || cache.length() + sig.length() + 1 > options.getWidth())) {
                    // Flush line if next description doesn't fit in
                    sb.append(cache).append("\n");
                    cache = options.getSignIndentStr();
                }
                if (cache.trim().length() != 0) {
                    cache += " ";
                }
                cache += sig;
            }
        }

        // Display unnamed option
        EnumSet<UnnamedOptionMultiplicity> unnamedMultiplicity = optionSet.getUnnamedMultiplicity();
        if (unnamedMultiplicity.contains(UnnamedOptionMultiplicity.ENABLED)) {
            String unnamed = "";

            if (unnamedMultiplicity.contains(UnnamedOptionMultiplicity.OPTIONAL)) {
                unnamed = "[";
            }
            unnamed += optionSet.getUnnamedValueName();
            if (unnamedMultiplicity.contains(UnnamedOptionMultiplicity.MULTIPLE)) {
                unnamed += " ...";
            }

            if (unnamedMultiplicity.contains(UnnamedOptionMultiplicity.OPTIONAL)) {
                unnamed += "]";
            }

            if (cache.length() != 0 && (options.isSignatureSplit() || cache.length() + unnamed.length() + 1 > options.getWidth())) {
                // Flush line if next description doesn't fit in
                sb.append(cache).append("\n");
                cache = options.getSignIndentStr();
            } else {
                cache += " ";
            }
            cache += unnamed;
        }

        // Display internal option section
        if (options.isShowInternalParametersInSignature()) {
            String internal = INTERNAL_OPTION_SECTION;

            if (cache.length() != 0 && (options.isSignatureSplit() || cache.length() + internal.length() + 1 > options.getWidth())) {
                sb.append(cache).append("\n");
                cache = options.getSignIndentStr();
            } else {
                cache += " ";
            }
            cache += internal;
        }


        if (cache.length() != 0) {
            sb.append(cache).append("\n");
        }
    }

    private Comparator<CommandLineOption<?>> SHORT_FORM_COMPARATOR = new Comparator<CommandLineOption<?>>() {

        @Override
        public int compare(CommandLineOption<?> o1, CommandLineOption<?> o2) {
            if (o1.hasShortForm() && o2.hasShortForm()) {
                return o1.getShortForm() - o2.getShortForm();
            } else if (o1.hasShortForm()) {
                return -1;
            } else if (o2.hasShortForm()) {
                return 1;
            } else {
                return o1.getLongForm().compareTo(o2.getLongForm());
            }

        }
    };

    private Comparator<CommandLineOption<?>> LONG_FORM_COMPARATOR = new Comparator<CommandLineOption<?>>() {

        @Override
        public int compare(CommandLineOption<?> o1, CommandLineOption<?> o2) {
            return o1.getLongForm().compareTo(o2.getLongForm());
        }
    };

    protected List<CommandLineOption<?>> sortOptionSet(OptionSet optionSet, SortMethod sortMethod) {
        List<CommandLineOption<?>> res = new ArrayList<>(optionSet.getOptions());
        if (sortMethod == SortMethod.SHORT_FORM) {
            Collections.sort(res, SHORT_FORM_COMPARATOR);
        }
        if (sortMethod == SortMethod.LONG_FORM) {
            Collections.sort(res, LONG_FORM_COMPARATOR);
        }
        return res;
    }

    private String getOptionSignature(CommandLineOption<?> o) {
        StringBuilder sb = new StringBuilder();
        boolean optional = false;
        if (o.getDefaultOptionPolicy() == OptionPolicy.OPTIONAL || o.getDefaultOptionPolicy() == OptionPolicy.OPTIONAL_AUTO) {
            optional = true;
        }
        if (optional) {
            sb.append('[');
        }
        if (o.hasShortForm()) {
            sb.append("{-").append(o.getShortForm()).append(" --").append(o.getLongForm()).append('}');
        } else {
            sb.append("--").append(o.getLongForm());
        }
        if (o.getValuePolicy() != ValuePolicy.NONE) {
            sb.append(' ');
            if (o.getValuePolicy() == ValuePolicy.OPTIONAL) {
                sb.append('[');
            }
            sb.append('<').append(o.getValueName()).append('>');
            if (o.getValuePolicy() == ValuePolicy.OPTIONAL) {
                sb.append(']');
            }
        }

        if (optional) {
            sb.append(']');
        }
        return sb.toString();
    }


    /*
     * (non-Javadoc)
     * 
     * @see
     * hu.vissy.yacap.display.HelpRenderer#renderDetails(hu.vissy.yacap.OptionSet
     * , java.lang.StringBuilder)
     */
    @Override
    public StringBuilder renderDetails(OptionSet optionSet, StringBuilder sb) {
        StringWrapperOptions woIndented = new StringWrapperOptions.Builder().withWidth(options.getWidth()).withIndent(options.getIndent()).getInstance();
        StringWrapperOptions woNormal = new StringWrapperOptions.Builder().withWidth(options.getWidth()).getInstance();

        if (optionSet.getDescriptionPrologue() != null) {
            sb.append(StringUtils.wrapString(optionSet.getDescriptionPrologue(), woNormal)).append("\n");
        }

        List<CommandLineOption<?>> sortedOptions = sortOptionSet(optionSet, options.getDetailsSortMethod());

        for (CommandLineOption<?> o : sortedOptions) {
            String form = "";
            if (o.hasShortForm()) {
                form += "-" + o.getShortForm() + " ";
            }
            form += "--" + o.getLongForm();

            sb.append(form);
            sb.append("\n");
            String desc = o.getDescription();
            if (o.getDefaultValue() != null) {
                desc += " (Default: " + o.getDefaultValue() + ")";
            }
            sb.append(StringUtils.wrapString(desc, woIndented));
            if (options.isSeparateBlocks()) {
                sb.append("\n");
            }
        }

        // Unnamed options
        if (optionSet.getUnnamedMultiplicity().contains(UnnamedOptionMultiplicity.ENABLED) &&
                options.isShowUnnamedDescription() && optionSet.getUnnamedDescription() != null) {
            sb.append(optionSet.getUnnamedValueName());
            sb.append("\n");
            sb.append(StringUtils.wrapString(optionSet.getUnnamedDescription(), woIndented));
            if (options.isSeparateBlocks()) {
                sb.append("\n");
            }
        }

        if (optionSet.getDescriptionEpilogue() != null) {
            sb.append(StringUtils.wrapString(optionSet.getDescriptionEpilogue(), woNormal)).append("\n");
        }
        return sb;
    }


}
